Merge dotnet/java-interop into external/Java.Interop with full history#11744
Conversation
Fixes: dotnet/java-interop#1071 The latest API docs update contained a couple dozen parsing issues due to `<code/>` parsing, including: * Closing element doesn't match opening element: `<code>null</null>` * Content including `@`: `<code>android:label="@string/resolve_title"</code>` * Closing element is actually an opening element: `<code>Activity.RESULT_OK<code>` * Improper element nesting: `<code><pre><p>content</code></pre></p>` * Use of attributes: `<code class=prettyprint>content<code>` Fix this by replacing `CodeElementDeclaration` to use a new `CodeElementContentTerm` terminal, which is a "greedy regex" which grabs `<code` until one of: * `</code>` * `</null>` * `<code>` The result of `CodeElementDeclaration` is the end of the `<code>` element until the beginning of one of the above terminators: * `<code>null</null>` becomes `<c>null</c>` * `<code>android:label="@string/resolve_title"</code>` becomes `<c>android:label="@string/resolve_title"</c>`.` * `<code>Activity.RESULT_OK<code>` becomes `<c>Activity.RESULT_OK</c>`. * `<code><pre><p>content</code></pre></p>` becomes the mess `<c><pre><p>some content</c></pre></p>` 🤷♂️ * `<code class=prettyprint>content<code>` becomes `<c>content</c>`.`
Context: a8d50a5 Context: ff1855f As part of the Nullable Reference Type work in ff1855f, we added a BG8A08 warning when the `//*/@path` attribute of a "metadata" element is not provided. However, metadata files can also contain `<ns-replace/>` elements, which do not have a `@path` attribute, and thus erroneously trigger this warning: generated\msbuild-metadata.xml(3,4): warning BG8A08: Metadata.xml element '<ns-replace source="com.google.androidx" replacement="Xamarin.AndroidX" />' is missing the 'path' attribute. Skip running any of the standard "metadata" logic when we hit a `<ns-replace>` element, as they are handled elsewhere.
…1137) As we consume nightly .NET 8 builds, they sometimes depend on nightly .NET 7 builds. One error you can run into is: error NU1102: Unable to find package Microsoft.AspNetCore.App.Ref with version (= 7.0.11) error NU1102: Unable to find package Microsoft.WindowsDesktop.App.Ref with version (= 7.0.11) For projects that are not even ASP.NET or Windows desktop apps! To even be able to access these feeds, they would need to be an entry within `NuGet.config` similar to <packageSources> <clear/> <add key="darc-pub-dotnet-aspnetcore-[SHA]" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-aspnetcore-[SHA]/nuget/v3/index.json" /> <add key="darc-pub-dotnet-windowsdesktop-[SHA]" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-windowsdesktop-[SHA]/nuget/v3/index.json" /> </packageSources> We don't currently track these packages, because we don't actually use them. The .NET SDK team has provided a setting to workaround this, [`$(DisableTransitiveFrameworkReferenceDownloads)`][0], we have been [using in xamarin/xamarin-android for some time][1]. Let's do the same here to avoid this problem as seen in 4f9dbce6. [0]: https://learn.microsoft.com/en-us/dotnet/core/project-sdk/msbuild-props#disabletransitiveframeworkreferencedownloads [1]: https://github.com/xamarin/xamarin-android/blob/6768c731d327c8148c45304c895ca8987a9cc2f1/Directory.Build.props#L26-L27
…1138) Context: #8279 This reverts commit 83b5089. Near the end of .NET 8 RC 1, the .NET 8 SDK depends on nightly packages for .NET 6 and .NET 7: * dotnet/runtime 7.0.11 * dotnet/runtime 6.0.22 These come from feeds within `NuGet.config` such as: <packageSources> <clear/> <!-- Added manually for dotnet/runtime 7.0.11 --> <add key="darc-pub-dotnet-runtime-a2ad4f0" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-runtime-a2ad4f03/nuget/v3/index.json" /> <!-- Added manually for dotnet/runtime 6.0.22 --> <add key="darc-pub-dotnet-runtime-762f437" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-runtime-762f4379/nuget/v3/index.json" /> </packageSources> The version number is stable, so the .NET releng team has infrastructure to create a new feed per git commit. Unfortunately, this makes NuGet central package management unusable for us. 😢 As seen in #8279, we get errors like: Package source mapping matches found for package ID 'Microsoft.NETCore.App.Ref' are: 'dotnet-public'. … external\Java.Interop\src\Java.Interop.Tools.Expressions\Java.Interop.Tools.Expressions.csproj error NU1102: Unable to find package Microsoft.NETCore.App.Ref with version (= 7.0.11) - Found 83 version(s) in dotnet-public [ Nearest version: 8.0.0-preview.1.23110.8 ] - Versions from dotnet-eng were not considered [Xamarin.Android.sln] This is because it can only possibly resolve this package from `dotnet-public`: <packageSourceMapping> <packageSource key="dotnet-public"> <package pattern="*" /> </packageSource> </packageSourceMapping> Because 7.0.11 hasn't shipped yet, it is on neither NuGet.org nor `dotnet-public`. For this to work, we would somehow need this `NuGet.config` fragment to be added to the xamarin/java.interop repo whenever xamarin/xamarin-android gets a newer .NET 8 SDK: <packageSourceMapping> <packageSource key="darc-pub-dotnet-runtime-[HASH]"> <package pattern="Microsoft.NETCore.App.Ref" /> </packageSource> </packageSourceMapping> For now, let's revert 83b5089. Maybe there is some solution we can come up with to use this in the future.
Update `<JdkInfo/>` task to emit new `$(Java*MajorVersion)`
and `$(JavaApi*DefineConstants)` MSBuild properties. These are used
by `src/Java.Base` so that it knows which JDK version it's binding.
Update `src/Java.Base` to support binding the `java.base.jmod` from
JDK 17.
Note: This "JDK-17 Java.Base binding" was a "time limited" effort.
To build against JDK-17:
1. Install JDK-17.
2. Prepare and override `$(JdksRoot)`:
dotnet build -t:Prepare Java.Interop.sln -p:JdksRoot=/Library/Java/JavaVirtualMachines/microsoft-17.jdk/Contents/Home
will use the Microsoft OpenJDK 17 installation on macOS.
3. Build:
dotnet build Java.Interop.sln
Changes: dotnet/android-tools@3cee10b...9c50a2d * dotnet/android-tools@9c50a2d: [build] set `$(DisableTransitiveFrameworkReferenceDownloads)`=true (dotnet/android-tools#216) * dotnet/android-tools@52f0866: [Xamarin.Android.Tools.AndroidSdk] Check all <intent-filter/>s (dotnet/android-tools#214) * dotnet/android-tools@57be026: [Xamarin.Android.Tools.AndroidSdk] Update SDK component for API-34 (dotnet/android-tools#211) * dotnet/android-tools@0a9ea47: [Xamarin.Android.Tools.AndroidSdk] Add API-34 to KnownVersions (dotnet/android-tools#212) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Context: https://discord.com/channels/732297728826277939/732297837953679412/1151531791069499524 A user reported the following output in their `java-resolution-report.log`: The field 'Java.Interop.Tools.JavaTypeSystem.Models.JavaFieldModel' was removed because its name contains a dollar sign. The class '[Class] com.google.android.libraries.navigation.internal.aac.ad' was removed because the Java base type 'com.google.android.libraries.navigation.internal.aad.ar<com.google.android.libraries.navigation.internal.aac.af<K, V>>' could not be found. We should be providing the user with the name of the removed field rather than the `JavaFieldModel` type name. To do this, add an appropriate `JavaFieldModel.ToString ()` method override.
Changes: dotnet/android-tools@9c50a2d...8a971d9 * dotnet/android-tools@8a971d9: Merge pull request dotnet/android-tools#217 from xamarin/dev/tondat/main-openjdkms * dotnet/android-tools@42bbef8: Update OpenJDK location for OpenJDK17 on windows Adds support to look for JDK installation on Windows within `%ProgramFiles%\Android\openjdk\jdk-*`. Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Context: #8360 Context: c1ff50a Context: f91da28 Context: dotnet/android-tools@34e98e2 When .NET 8 RC 2 took a dependency on dotnet/runtime 7.0.12 and 6.0.23, we added `external/xamarin-android-tools.override.props` in #8360 with the contents: <Project> <PropertyGroup> <RestoreAdditionalProjectSources> https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-runtime-26e0f822/nuget/v3/index.json; https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-runtime-301ba1ee/nuget/v3/index.json; </RestoreAdditionalProjectSources> </PropertyGroup> </Project> This allowed xamarin-android/external/xamarin-android-tools to find and use the new NuGet sources, but xamarin-android/external/Java.Interop uses its own checkout of xamarin-android-tools in `xamarin-android/external/java.interop/external/xamarin-android-tools`.` This led to the error during the `prepare java.interop Debug` stage: tests/api-compatibility/api-compatibility.targets(3,3): warning MSB4011: "Configuration.props" cannot be imported again. It was already imported at "build-tools/scripts/RunTests.targets (7,3)". This is most likely a build authoring error. This subsequent import will be ignored. external/Java.Interop/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj : error NU1102: Unable to find package Microsoft.NETCore.App.Ref with version (= 6.0.23) external/Java.Interop/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj : error NU1102: - Found 86 version(s) in dotnet-public [ Nearest version: 7.0.0-preview.1.22076.8 ] external/Java.Interop/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj : error NU1102: - Found 1 version(s) in dotnet-eng [ Nearest version: 5.0.0-alpha.1.19618.1 ] external/Java.Interop/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj : error NU1102: Unable to find package Microsoft.NETCore.App.Ref with version (= 6.0.23) external/Java.Interop/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj : error NU1102: - Found 86 version(s) in dotnet-public [ Nearest version: 7.0.0-preview.1.22076.8 ] external/Java.Interop/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj : error NU1102: - Found 1 version(s) in dotnet-eng [ Nearest version: 5.0.0-alpha.1.19618.1 ] build-tools/scripts/DotNet.targets(19,5): error MSB3073: The command ""bin/Release/dotnet/dotnet" build -t:Prepare Java.Interop.sln -c Debug -p:JdksRoot= -p:DotnetToolPath=bin/Release/dotnet/dotnet -bl:build-tools/scripts/../../bin/BuildDebug/msbuild-20230925T183954-prepare-java-interop.binlog" exited with code 1. 1 Warning(s) 7 Error(s) Introduce an `external/xamarin-android-tools.override.props` within Java.Interop which imports `..\Directory.Build.props`. This allows MSBuild properties to "flow" from a "parent" `xamarin-android/external/Java.Interop.override.props` through to `xamarin-android/external/Java.Interop/external/xamarin-android-tools`, allowing a xamarin-android checkout to more easily control MSBuild properties used by xamarin-android-tools.
…1143) Fixes: dotnet/java-interop#1142 Context: b116a4b In b116a4b, we added a feature to automatically mark a method as "deprecated" if it overrides a "deprecated" method. However, there can be instances where this is undesirable for a user, and there is currently no way to opt out of this change on a global or `metadata` level. Add a global opt-out for this feature, usable via `generator --lang-features=do-not-fix-obsolete-overrides …`. This will eventually be made available to users via an MSBuild property in a separate xamarin/xamarin-android PR.
Fixes: #8337 A customer's app with the code: SetContentView(Resource.Layout.activity_main); FindViewById<Button>(Resource.Id.asd).Click += MainActivity_Click; Crashes with `-c Release -p:AndroidLinkMode=r8` with: java.lang.RuntimeException: Unable to start activity ComponentInfo{com.companyname.New_folder/crc64abe8cc9139195b67.MainActivity}: java.lang.ClassNotFoundException: android.view.View_IOnClickListenerImplementor at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3644) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3781) at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:101) at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:138) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2306) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loopOnce(Looper.java:201) at android.os.Looper.loop(Looper.java:288) at android.app.ActivityThread.main(ActivityThread.java:7918) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936) Caused by: java.lang.ClassNotFoundException: android.view.View_IOnClickListenerImplementor at crc64abe8cc9139195b67.MainActivity.n_onCreate(Native Method) at crc64abe8cc9139195b67.MainActivity.onCreate(MainActivity.java:30) at android.app.Activity.performCreate(Activity.java:8342) at android.app.Activity.performCreate(Activity.java:8321) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1417) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3625) ... 12 more Which, can be solved by adding your own `proguard` rules like: -keep class android.view.View_IOnClickListenerImplementor { *; } In a8cbe01, we thought (incorrectly): > Additionally, stop emitting the `[Register]` attribute for > `*Implementor` classes: > > [global::Android.Runtime.Register ("mono/android/view/View_OnFocusChangeListenerImplementor")] > partial class IOnFocusChangeListenerImplementor {/* … */} > > The `[Register]` attribute is not needed, because `*Implementor` > classes are generated internal implementation details. `proguard_xamarin.cfg` has the entry: -keep class mono.android.** { *; <init>(...); } We could do `-keep class android.**` [^0], but that would certainly preserve way too much! For now, let's just restore `[Register]` to revisit this issue at a later date -- maybe .NET 9? [^0]: Why is `View_IOnClickListenerImplementor` in the `android.view` package?! Because package name generation for Java Callable Wrappers is special-cased for `Mono.Android` to simply [lowercase the namespace name][0], which applies only if the type *doesn't* have `[RegisterAttribute]`! [0]: https://github.com/xamarin/java.interop/blob/009f9c03317d1ef0c0a66fffe2b5c654cb9fd2e1/src/Java.Interop.Tools.TypeNameMappings/Java.Interop.Tools.TypeNameMappings/JavaNativeTypeManager.cs#L200-L209
Context: 59c5d93 Context: https://discord.com/channels/732297728826277939/732297837953679412/1162418256615846030 Context: https://discord.com/channels/732297728826277939/732297837953679412/1162422742256205954 A customer reports that when binding [androidx.emoji2.emoji2-emojipicker-1.4.0][0], [`EmojiPickerView.setOnEmojiPickedListener()`][1] wasn't being bound. Subsequent investigation showed that it wasn't being bound because it had `//method[@visibility="kotlin-internal"]`. …but *why* did `class-parse` indicate that `EmojiPickerView.setOnEmojiPickedListener()` has visibility of `kotlin-internal` and not `public`? In order to assist this question, update `class-parse -dump` to also dump out the parsed Kotlin metadata blob. Example: % dotnet …/class-parse.dll -dump EmojiPickerView.class … Kotlin Class Metadata [1.8.0]: { "$id": "1", "CompanionObjectName": "Companion", "Constructors": { ... }, "EnumEntries": { "$id": "14", "$values": [] }, "Flags": 6, "FullyQualifiedName": null, "Inheritability": 0, "NestedClassNames": { "$id": "15", "$values": [ "Companion" ] }, "ObjectType": 0, "SealedSubclassFullyQualifiedNames": null, "SuperTypeIds": null, "SuperTypes": { "$id": "16", "$values": [ { "$id": "17", "Arguments": { "$id": "18", "$values": [] }, "Nullable": false, "FlexibleTypeCapabilitiesId": null, "FlexibleUpperBound": null, "FlexibleUpperBoundId": 0, "ClassName": "android/widget/FrameLayout;", "TypeParameter": null, "TypeParameterName": null, "TypeAliasName": null, "OuterType": null, "OuterTypeId": null, "AbbreviatedType": null, "AbbreviatedTypeId": null, "Flags": 0 } ] }, "TypeParameters": { "$id": "19", "$values": [] }, "VersionRequirements": null, "Visibility": 3, "Functions": { "$id": "20", "$values": [ ... { "$id": "145", "Name": "setOnEmojiPickedListener", "JvmName": "setOnEmojiPickedListener", "JvmSignature": null, "Flags": 6, "ReturnType": { "$id": "146", "Arguments": { "$id": "147", "$values": [] }, "Nullable": false, "FlexibleTypeCapabilitiesId": null, "FlexibleUpperBound": null, "FlexibleUpperBoundId": 0, "ClassName": "kotlin/Unit", "TypeParameter": null, "TypeParameterName": null, "TypeAliasName": null, "OuterType": null, "OuterTypeId": null, "AbbreviatedType": null, "AbbreviatedTypeId": null, "Flags": 0 }, "ReturnTypeId": 0, "TypeParameters": { "$id": "148", "$values": [] }, "ReceiverType": null, "ReceiverTypeId": 0, "TypeTable": null, "Contract": null, "ValueParameters": { "$id": "149", "$values": [ { "$id": "150", "Flags": 0, "Name": "onEmojiPickedListener", "Type": { "$id": "151", "Arguments": { "$id": "152", "$values": [ { "$id": "153", "Projection": 2, "Type": { "$id": "154", "Arguments": { "$id": "155", "$values": [] }, "Nullable": false, "FlexibleTypeCapabilitiesId": null, "FlexibleUpperBound": null, "FlexibleUpperBoundId": 0, "ClassName": "androidx/emoji2/emojipicker/EmojiViewItem;", "TypeParameter": null, "TypeParameterName": null, "TypeAliasName": null, "OuterType": null, "OuterTypeId": null, "AbbreviatedType": null, "AbbreviatedTypeId": null, "Flags": 0 }, "TypeId": 0 } ] }, "Nullable": true, "FlexibleTypeCapabilitiesId": null, "FlexibleUpperBound": null, "FlexibleUpperBoundId": 0, "ClassName": "androidx/core/util/Consumer;", "TypeParameter": null, "TypeParameterName": null, "TypeAliasName": null, "OuterType": null, "OuterTypeId": null, "AbbreviatedType": null, "AbbreviatedTypeId": null, "Flags": 0 }, "TypeId": 0, "VarArgElementType": null, "VarArgElementTypeId": 0 } ] }, "VersionRequirements": null }, ... }, "Properties": {... }, "TypeAliases": { "$id": "230", "$values": [] }, "TypeTable": null, "VersionRequirementTable": { "$id": "231", "Requirements": { "$id": "232", "$values": [ { "$id": "233", "Version": 25, "VersionFull": 0, "Level": 1, "ErrorCode": 0, "Message": 0, "VersionKind": 0 } ] } } } Kotlin Metadata String Table: [ "Landroidx/emoji2/emojipicker/EmojiPickerView;", "Landroid/widget/FrameLayout;", "context", "Landroid/content/Context;", "attrs", "Landroid/util/AttributeSet;", "defStyleAttr", "", "(Landroid/content/Context;Landroid/util/AttributeSet;I)V", "_emojiGridRows", "", "Ljava/lang/Float;", "bodyAdapter", "Landroidx/emoji2/emojipicker/EmojiPickerBodyAdapter;", "value", "emojiGridColumns", "getEmojiGridColumns", "()I", "setEmojiGridColumns", "(I)V", "emojiGridRows", "getEmojiGridRows", "()F", "setEmojiGridRows", "(F)V", "emojiPickerItems", "Landroidx/emoji2/emojipicker/EmojiPickerItems;", "onEmojiPickedListener", "Landroidx/core/util/Consumer;", "Landroidx/emoji2/emojipicker/EmojiViewItem;", "recentEmojiProvider", "Landroidx/emoji2/emojipicker/RecentEmojiProvider;", "recentItemGroup", "Landroidx/emoji2/emojipicker/ItemGroup;", "recentItems", "", "Landroidx/emoji2/emojipicker/EmojiViewData;", "recentNeedsRefreshing", "", "scope", "Lkotlinx/coroutines/CoroutineScope;", "stickyVariantProvider", "Landroidx/emoji2/emojipicker/StickyVariantProvider;", "addView", "", "child", "Landroid/view/View;", "params", "Landroid/view/ViewGroup$LayoutParams;", "index", "width", "height", "buildEmojiPickerItems", "buildEmojiPickerItems$emoji2_emojipicker_release", "createEmojiPickerBodyAdapter", "refreshRecent", "refreshRecent$emoji2_emojipicker_release", "(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;", "removeAllViews", "removeView", "removeViewAt", "removeViewInLayout", "removeViews", "start", "count", "removeViewsInLayout", "setOnEmojiPickedListener", "setRecentEmojiProvider", "showEmojiPickerView", "Companion", "emoji2-emojipicker_release" ] [0]: https://maven.google.com/web/index.html#androidx.emoji2:emoji2-emojipicker:1.4.0 [1]: https://developer.android.com/develop/ui/views/text-and-emoji/emoji-picker#how-use
Fixes: dotnet/java-interop#1139 Context: https://jetbrains.gitbooks.io/kotlin-reference-for-kindle/content/properties.html Kotlin does not allow classes to explicitly contain fields: > Classes in Kotlin cannot have fields. They can *implicitly* contain fields, but not explicitly. Syntax that *look like* a field to those who don't know Kotlin: /* partial */ class EmojiPickerView { private var onEmojiPickedListener: Consumer<EmojiViewItem>? = null } are in fact *properties*, which may or may not involve a compiler- generated backing field. When a property is declared in Kotlin, Java `get*` and/or `set*` methods are generated as needed. In the case where the property is `internal` -- which is not supported by Java -- `public` getters or setters are generated. We wish to "hide" these as they are not intended to be part of the public API. That is, they would not be callable by Kotlin code. However, consider these code fragments from [`EmojiPickerView.kt`][0]: /* partial */ class EmojiPickerView { // Line 100 private var onEmojiPickedListener: Consumer<EmojiViewItem>? = null // Lines 317-319 fun setOnEmojiPickedListener(onEmojiPickedListener: Consumer<EmojiViewItem>?) { this.onEmojiPickedListener = onEmojiPickedListener } } Default member visibility in Kotlin is `public`, so this declares a private `onEmojiPickedListener` property and a public `setOnEmojiPickedListener()` method. However, we were incorrectly determining visibility (59c5d93): we treated `onEmojiPickedListener` and `setOnEmojiPickedListener()` as if they were part of the same property. As part of this association, we saw that `onEmojiPickedListener` was private, and marked `setOnEmojiPickedListener()` as private to follow suit. This was incorrect, because `private` properties do not have setters *at all*, so we should not attempt to hide anything for `private` properties, only for `internal` properties. With that incorrect association broken, that allows `setOnEmojiPickedListener()` to be considered separately, and found to have `public` visibility. For example, this Kotlin code: private var type = 0 internal var itype = 0 generates Java code equivalent to: private int type; private int itype; public final int getItype$main() { return this.itype; } public final void setItype$main(int <set-?>) { this.itype = <set-?>; Additionally, when matching `internal` properties to their getters or setters, the generated name differs from the names given to `public` getters and setters. // Kotlin: public var type = 0 // Java: public final int getType () { ... } // Kotlin: internal var type = 0 // Java: public final int getType$main () { ... } Fix this scenario: * Do not attempt to hide getters/setters for `private` Kotlin properties. * Improve matching of generated names for `internal` Kotlin properties. Additionally, our existing unit test in `NameShadowing.kt` was written incorrectly: // Incorrect, return type of a function fun setType(type: Int) = { println (type); } // Correct, return type of void fun setType(type: Int) { println (type); } The fixed `NameShadowing.kt` unit test fails without the other changes here. Additionally, add several more unit test cases to cover `internal` properties and mangled getter/setter names. Additionally, some enum values in `KotlinPropertyFlags` were specified incorrectly which has been fixed. [0]: https://github.com/androidx/androidx/blob/0d655214d339e006f4e13a85f55c78770c885f2e/emoji2/emoji2-emojipicker/src/main/java/androidx/emoji2/emojipicker/EmojiPickerView.kt
#1145) Fixes: dotnet/java-interop#910 Context: d0996b0 Context: https://github.com/xamarin/java.interop/issues/858 Consider the Java `java.lang.Runnable` interface: package java.lang; public interface Runnable { void run (); } This is bound as: package Java.Lang; public interface IRunnable : IJavaPeerable { void Run (); } with some slight differences depending on whether we're dealing with .NET Android (`generator --codegen-target=xajavainterop1`) or `src/Java.Base` (`generator --codegen-target=javainterop1`). Now, assume a Java API + corresponding binding which returns a `Runnable` instance: package example; public class Whatever { public static Runnable createRunnable(); } You can invoke `IRunnable.Run()` on the return value: IRunnable r = Whatever.CreateRunnable(); r.Run(); but how does that work? This works via an "interface Invoker", which is a class emitted by `generator` which implements the interface and invokes the interface methods through JNI: internal partial class IRunnableInvoker : Java.Lang.Object, IRunnable { public void Run() => … } Once Upon A Time™, the interface invoker implementation mirrored that of classes: a static `IntPtr` field held the `jmethodID` value, which would be looked up on first-use and cached for subsequent invocations: partial class IRunnableInvoker { static IntPtr id_run; public unsafe void Run() { if (id_run == IntPtr.Zero) id_run = JNIEnv.GetMethodID (class_ref, "run", "()V"); JNIEnv.CallVoidMethod (Handle, id_run, …); } } This approach works until you have interface inheritance and methods which come from inherited interfaces: package android.view; public /* partial */ interface ViewManager { void addView(View view, ViewGroup.LayoutParams params); } public /* partial */ interface WindowManager extends ViewManager { void removeViewImmediate(View view); } This would be bound as: namespace Android.Views; public partial interface IViewManager : IJavaPeerable { void AddView (View view, ViewGroup.LayoutParams @params); } public partial IWindowManager : IViewManager { void RemoveViewImmediate (View view); } internal partial class IWindowManagerInvoker : Java.Lang.Object, IWindowManager { static IntPtr id_addView; public void AddView(View view, ViewGroup.LayoutParams @params) { if (id_addView == IntPtr.Zero) id_run = JNIEnv.GetMethodID (class_ref, "addView", "…"); JNIEnv.CallVoidMethod (Handle, id_addView, …); } } Unfortunately, *invoking* `IViewManager.AddView()` through an `IWindowManagerInvoker` would crash! D/dalvikvm( 6645): GetMethodID: method not found: Landroid/view/WindowManager;.addView:(Landroid/view/View;Landroid/view/ViewGroup$LayoutParams;)V I/MonoDroid( 6645): UNHANDLED EXCEPTION: Java.Lang.NoSuchMethodError: Exception of type 'Java.Lang.NoSuchMethodError' was thrown. I/MonoDroid( 6645): at Android.Runtime.JNIEnv.GetMethodID (intptr,string,string) I/MonoDroid( 6645): at Android.Views.IWindowManagerInvoker.AddView (Android.Views.View,Android.Views.ViewGroup/LayoutParams) I/MonoDroid( 6645): at Mono.Samples.Hello.HelloActivity.OnCreate (Android.OS.Bundle) I/MonoDroid( 6645): at Android.App.Activity.n_OnCreate_Landroid_os_Bundle_ (intptr,intptr,intptr) I/MonoDroid( 6645): at (wrapper dynamic-method) object.ecadbe0b-9124-445e-a498-f351075f6c89 (intptr,intptr,intptr) Interfaces are not classes, and this is one of the places that this is most apparent. Because of this crash, we had to use *instance* `jmethodID` caches: internal partial class IWindowManagerInvoker : Java.Lang.Object, IWindowManager { IntPtr id_addView; public void AddView(View view, ViewGroup.LayoutParams @params) { if (id_addView == IntPtr.Zero) id_run = JNIEnv.GetMethodID (class_ref, "addView", "…"); JNIEnv.CallVoidMethod (Handle, id_addView, …); } } Pro: no more crash! Con: *every different instance* of `IWindowManagerInvoker` needs to separately lookup whatever methods are invoked. There is *some* caching, so repeated calls to `AddView()` on the same instance will hit the cache, but if you obtain a different `IWindowManager` instance, `jmethodID` values will need to be looked up again. This was "fine", until xamarin/java.interop#858 enters the picture: interface invokers were full of Android-isms -- `Android.Runtime.JNIEnv.GetMethodID()`! `JNIEnv.CallVoidMethod()`! -- and thus ***not*** APIs that @jonpryor wished to expose within desktop Java.Base bindings. Enter `generator --lang-features=emit-legacy-interface-invokers`: when *not* specified, interface invokers will now use `JniPeerMembers` for method lookup and invocation, allowing `jmethodID` values to be cached *across* instances. In order to prevent the runtime crash, an interface may have *multiple* `JniPeerMembers` values, one per implemented interface, which is used to invoke methods from that interface. `IWindowManagerInvoker` now becomes: internal partial class IWindowManagerInvoker : Java.Lang.Object, IWindowManager { static readonly JniPeerMembers _members_android_view_ViewManager = …; static readonly JniPeerMembers _members_android_view_WindowManager = …; public void AddView(View view, ViewGroup.LayoutParams @params) { const string __id = "addView.…"; _members_android_view_ViewManager.InstanceMethods.InvokeAbstractVoidMethod (__id, this, …); } public void RemoveViewImmediate(View view) { const string __id = "removeViewImmediate.…"; _members_android_view_WindowManager.InstanceMethods.InvokeAbstractVoidMethod (__id, this, …); } } This has two advantages: 1. More caching! 2. Desktop `Java.Base` binding can now have interface invokers. Update `tests/generator-Tests` expected output. Note: to keep this patch smaller, JavaInterop1 output uses the new pattern, and only *some* XAJavaInterop1 tests use the new pattern. Added [CS0114][0] to `$(NoWarn)` in `Java.Base.csproj` to ignore warnings such as: …/src/Java.Base/obj/Debug-net7.0/mcw/Java.Lang.ICharSequence.cs(195,25): warning CS0114: 'ICharSequenceInvoker.ToString()' hides inherited member 'Object.ToString()'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword. [Ignoring CS0114 is also done in `Mono.Android.dll` as well][1], so this is not a new or unique requirement. Update `Java.Interop.dll` so that `JniRuntime.JniValueManager.GetActivationConstructor()` now knows about and looks for `*Invoker` types, then uses the activation constructor from the `*Invoker` type when the source type is an abstract `class` or `interface`. Update `tests/Java.Base-Tests` to test for implicit `*Invoker` lookup and invocation support. ~~ Property Setters ~~ While testing on #8339, we hit this error (among others, to be addressed later): src/Mono.Android/obj/Debug/net8.0/android-34/mcw/Android.Views.IWindowInsetsController.cs(304,41): error CS0103: The name 'behavior' does not exist in the current context This was caused because of code such as: public partial interface IWindowInsetsController { public unsafe int SystemBarsBehavior { get { const string __id = "getSystemBarsBehavior.()I"; try { var __rm = _members_IWindowInsetsController.InstanceMethods.InvokeAbstractInt32Method (__id, this, null); return __rm; } finally { } } set { const string __id = "setSystemBarsBehavior.(I)V"; try { JniArgumentValue* __args = stackalloc JniArgumentValue [1]; __args [0] = new JniArgumentValue (behavior); _members_IWindowInsetsController.InstanceMethods.InvokeAbstractVoidMethod (__id, this, __args); } finally { } } } } This happened because when emitting the property setter, we need to update the `set*` method's parameter name to be `value` so that the normal property setter body is emitted properly. Update `InterfaceInvokerProperty.cs` so that the parameter name is set to `value`. ~~ Performance ~~ What does this do for performance? Add a new `InterfaceInvokerTiming` test fixture to `Java.Interop-PerformanceTests.dll`, which: 1. "Reimplements" the "legacy" and "JniPeerMembers" Invoker strategies 2. For each Invoker strategy: a. Invokes a Java method which returns a `java.lang.Runnable` instance b. Invokes `Runnable.run()` on the instance returned by (2.a) …100 times. c. Repeat (2.a) and (2.b) 100 times. The result is that using `JniPeerMembers` is *much* faster: % dotnet build tests/Java.Interop-PerformanceTests/*.csproj && \ dotnet test --logger "console;verbosity=detailed" bin/TestDebug-net7.0/Java.Interop-PerformanceTests.dll --filter "Name~InterfaceInvokerTiming" … Passed InterfaceInvokers [1 s] Standard Output Messages: ## InterfaceInvokers Timing: instanceIds: 00:00:01.1095502 ## InterfaceInvokers Timing: peerMembers: 00:00:00.1400427 Using `JniPeerMembers` takes ~1/8th the time as using `jmethodID`s. TODO: something is *probably* wrong with my test -- reviews welcome! -- as when I increase the (2.b) iteration count, the `peerMembers` time is largely unchanged (~0.14s), while the `instanceIds` time increases linearly. *Something* is wrong there. I'm not sure what. (Or *nothing* is wrong, and instance `jmethodID` are just *that* bad.) [0]: https://learn.microsoft.com/en-us/dotnet/csharp/misc/cs0114 [1]: https://github.com/xamarin/xamarin-android/blob/d5c4ec09f7658428a10bbe49c8a7a3eb2f71cb86/src/Mono.Android/Mono.Android.csproj#L12C7-L12C7
Context: 5537aec In commit 5537aec, we updated the logic that matches a Kotlin public property getter/setter to a generated Java getter/setter that follows this pattern: // Kotlin public var type = 0 // Java private int type = 0; public int getType () { ... } public void setType (int p0) { ... } However, this caused unit tests in xamarin/xamarin-android to fail that when using Kotlin unsigned types: KotlinUnsignedTypesTests.cs: error CS0200: Property or indexer 'UnsignedInstanceMethods.UnsignedInstanceProperty' cannot be assigned to -- it is read only This is because properties that use Kotlin unsigned types append a `-<type-hash>` suffix to their getter/setter names: // Kotlin public var type: UInt = 0u // Java private int type = 0; public int getType-pVg5ArA () { ... } public void setType-WZ4Q5Ns (int p0) { ... } Update our Kotlin logic to handle this case.
Context: dotnet/java-interop#1153 [JNI][0] supports *two* modes of operation: 1. Native code creates the JVM, e.g. via [`JNI_CreateJavaVM()`][1] 2. The JVM already exists, and when Java code calls [`System.loadLibrary()`][3], the JVM calls the [`JNI_OnLoad()`][2] function on the specified library. Java.Interop samples and unit tests rely on the first approach, e.g. `TestJVM` subclasses `JreRuntime`, which is responsible for calling `JNI_CreateJavaVM()` so that Java code can be used. PR #1153 is exploring the use of [.NET Native AOT][4] to produce a native library which is used with Java-originated initialization. In order to make Java-originated initialization *work*, we need to be able to initialize `JniRuntime` and `JreRuntime` around existing JVM-provided pointers: * The `JavaVM*` provided to `JNI_OnLoad()`, which can be used to set `JniRuntime.CreationOptions.InvocationPointer`: [UnmanagedCallersOnly(EntryPoint="JNI_OnLoad")] int JNI_OnLoad(IntPtr vm, IntPtr reserved) { var options = new JreRuntimeOptions { InvocationPointer = vm, }; var runtime = options.CreateJreVM (); return runtime.JniVersion; return JNI_VERSION_1_6; } * The [`JNIEnv*` value provided to Java `native` methods][5] when they are invoked, which can be used to set `JniRuntime.CreationOptions.EnvironmentPointer`: [UnmanagedCallersOnly(EntryPoint="Java_example_Whatever_init")] void Whatever_init(IntPtr jnienv, IntPtr Whatever_class) { var options = new JreRuntimeOptions { EnvironmentPointer = jnienv, }; var runtime = options.CreateJreVM (); } Update `JniRuntime` and `JreRuntime` to support these Java-originated initialization strategies. In particular, don't require that `JreRuntimeOptions.JvmLibraryPath` be set, avoiding: System.InvalidOperationException: Member `JreRuntimeOptions.JvmLibraryPath` must be set. at Java.Interop.JreRuntime.CreateJreVM(JreRuntimeOptions builder) at Java.Interop.JreRuntime..ctor(JreRuntimeOptions builder) at Java.Interop.JreRuntimeOptions.CreateJreVM() [0]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/jniTOC.html [1]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/invocation.html#creating_the_vm [2]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Runtime.html#loadLibrary(java.lang.String) [3]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/invocation.html#JNJI_OnLoad [4]: https://learn.microsoft.com/dotnet/core/deploying/native-aot/ [5]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#native_method_arguments
Fixes: #7554 Placing `[Export]` on a constructor with parameters did not work as expected. Consider: public class Example : Java.Lang.Object { [Export(SuperArgumentsString = "")] public Example (int value) { this.value = value; } int value; } This *does* generate a Java Callable Wrapper with the desired constructor, but it *doesn't* invoke the correct C# constructor, because of the `TypeManager.Activate()` invocation: // Java JCW /* partial */ class Example extends java.lang.Object { public Example(int p0) { super (); if (getClass () == Example.class) { mono.android.TypeManager.Activate ( /* typeName */ "Namespace.Example, Assembly", /* signature */ "", /* instance */ this, /* parameterList */ new java.lang.Object[] { p0 }); } } } In particular, because `signature` is the empty string, `TypeManager.Activate()` will lookup and invoke the *default* constructor, rather than the `Example(int)` constructor. (This despite the fact that `parameterList` contains arguments!) Consequently, when Java invokes the `Example(int)` constructor, an exception is thrown, as the default constructor it wants doesn't exist: Could not activate JNI Handle 0x7ffd2bd94e50 (key_handle 0x65d856e) of Java type 'namespace/Example' as managed type 'Namespace.Example'. Java.Interop.JavaLocationException: Exception of type 'Java.Interop.JavaLocationException' was thrown. Java.Lang.Error: Exception of type 'Java.Lang.Error' was thrown. --- End of managed Java.Lang.Error stack trace --- java.lang.Error: Java callstack: at mono.android.TypeManager.n_activate(Native Method) at mono.android.TypeManager.Activate(TypeManager.java:7) at namespace.Example.<init>(Example.java:0) … Update `JavaCallableWrapperGenerator.AddConstructor()` so that the `managedParameters` value is provided to the `Signature` instance for the `[Export]` constructor. This value is then inserted as the `signature` parameter value, which will allow the correct constructor to be invoked: // Fixed Java JCW /* partial */ class Example extends java.lang.Object { public Example(int p0) { super (); if (getClass () == Example.class) { mono.android.TypeManager.Activate ( /* typeName */ "Namespace.Example, Assembly", /* signature */ "System.Int32, System.Runtime", /* instance */ this, /* parameterList */ new java.lang.Object[] { p0 }); } } }
Context: dotnet/java-interop#1153 Context: 58f41b8 Context: 16cd04f PR #1153 is exploring the use of [.NET Native AOT][0] to produce a native library which is used *within* a `java`-originated process: % java -cp … com/microsoft/hello_from_jni/App # launches NativeAOT-generated native lib, executes C# code… As NativeAOT has no support for `System.Reflection.Emit`, the only way for Java code to invoke managed code -- in a Desktop Java.Base environment! [^0] see 58f41b8 -- would be to pre-generate the required marshal methods via `jnimarshalmethod-gen`. This in turn requires updating `jcw-gen` to support the pre-existing `Java.Interop.JavaCallableAttribute`, so that C# code could reasonably declare methods visible to Java, along with the introduction of, and support for, a new `Java.Interop.JavaCallableConstructorAttribute` type. This allows straightforward usage: [JniTypeSignature ("example/ManagedType")] // for a nice Java name! class ManagedType : Java.Lang.Object { int value; [JavaCallableConstructor(SuperConstructorExpression="")] public ManagedType (int value) { this.value = value; } [JavaCallable ("getString")] public Java.Lang.String GetString () { return new Java.Lang.String ($"Hello from C#, via Java.Interop! Value={value}"); } } Run this through `jcw-gen` and `jnimarshalmethod-gen`, run the app, and nothing worked (?!), because not all pieces were in agreement. Java `native` method registration is One Of Those Things™ that involves lots of moving pieces: * `generator` emits bindings for Java types, which includes Java method names, signatures, and (on .NET Android) the "connector method" to use: [Register ("toString", "()Ljava/lang/String;", "GetToStringHandler")] // .NET Android [JniMethodSignature ("toString", "()Ljava/lang/String;")] // Java.Base public override unsafe string? ToString () {…} * `jcw-gen` uses `generator` output, *prefixing* Java method names with `n_` for `native` method declarations, along with a method wrapper [^1] public String toString() {return n_toString();} private native String n_toString(); * `jnimarshalmethod-gen` emits marshal methods for Java.Base, and needs to register the `native` methods declared by `jcw-gen`. `jnimarshalmethod-gen` and `jcw-gen` need to be consistent with each other. * `MarshalMemberbuilder.CreateMarshalToManagedMethodRegistration()` creates a `JniNativeMethodRegistration` instance which contains the name of the Java `native` method to register, and was using a name inconsistent with `jcw-gen`. Turns Out, `jcw-gen`, `jnimarshalmethod-gen`, and `MarshalMemberBuilder` were *not* consistent. The only "real" `jnimarshalmethod-gen` usage (16cd04f) is with the `Java.Interop.Export-Tests` unit test assembly, which *did not use* `jcw-gen`; it contained only hand-written Java code. Consequently, *none* of the Java `native` methods declared within it had an `n_` prefix, and since this worked with `jnimarshalmethod-gen`, this means that `jnimarshalmethod-gen` registration logic likewise didn't use `n_` prefixed method names. The result is that in the NativeAOT app, it would attempt to register the `native` Java method `ManagedType.getString()`, while what `jcw-gen` declared was `ManagedType.n_getString()`! Java promptly threw an exception, and the app crashed. Update `Java.Interop.Export-Tests` so that all the methods used with `MarshalMemberBuilder` are declared with `n_` prefixes, and add a `Java.Lang.Object` subclass example to the unit tests: Update `tests/Java.Interop.Tools.JavaCallableWrappers-Tests` to add a test for `.CodeGenerationTarget==JavaInterop1`. Add `$(NoWarn)` to `Java.Interop.Tools.JavaCallableWrappers-Tests.csproj` in order to "work around" warnings-as-errors: …/src/Java.Interop.NamingCustomAttributes/Java.Interop/ExportFieldAttribute.cs(19,63): error CA1019: Remove the property setter from Name or reduce its accessibility because it corresponds to positional argument name …/src/Java.Interop.NamingCustomAttributes/Android.Runtime/RegisterAttribute.cs(53,4): error CA1019: Remove the property setter from Name or reduce its accessibility because it corresponds to positional argument name …/src/Java.Interop.NamingCustomAttributes/Java.Interop/ExportFieldAttribute.cs(12,16): error CA1813: Avoid unsealed attributes … These are "weird"; the warnings/errors appear to come in because `Java.Interop.Tools.JavaCallableWrappers-Tests.csproj` now includes: <Compile Include="..\..\src\Java.Interop\Java.Interop\JniTypeSignatureAttribute.cs" /> which appears to pull in `src/Java.Interop/.editorconfig`, which makes CA1019 and CA1813 errors. (I do not understand what is happening.) Update `jnimarshalmethod-gen` so that the Java `native` methods it registers have an `n_` prefix. Refactor `ExpressionAssemblyBuilder.CreateRegistrationMethod()` to `ExpressionAssemblyBuilder.AddRegistrationMethod()`, so that the `EmitConsoleWriteLine()` invocation can provide the *full* type name of the `__RegisterNativeMembers()` method, which helps when there is more than one such method running around… Update `ExpressionAssemblyBuilder` so that the delegate types it creates for marshal method registration all have `[UnmanagedFunctionPointer(CallingConvention.Winapi)]`. (This isn't needed *here*, but is needed in the context of NativeAOT, as NativeAOT will only emit "marshal stubs" for delegate types which have `[UnmanagedFunctionPointer]`.) Unfortunately, adding `[UnmanagedFunctionPointer]` broke things: error JM4006: jnimarshalmethod-gen: Unable to process assembly '…/Hello-NativeAOTFromJNI.dll' Failed to resolve System.Runtime.InteropServices.CallingConvention Mono.Cecil.ResolutionException: Failed to resolve System.Runtime.InteropServices.CallingConvention at Mono.Cecil.Mixin.CheckedResolve(TypeReference self) at Mono.Cecil.SignatureWriter.WriteCustomAttributeEnumValue(TypeReference enum_type, Object value) … The problem is that `CallingConvention` was resolved from `System.Private.CoreLib`, and when we removed that assembly reference, the `CallingConvention` couldn't be resolved at all. We could "fix" this by explicitly adding a reference to `System.Runtime.InteropServices.dll`, but how many more such corner cases exist? The current approach is not viable. Remove the code from 16cd04f which attempts to remove `System.Private.CoreLib`. So long as `ExpressionAssemblyBuilder` output is *only* used in "completed" apps (not distributed in NuGet packages or some "intermediate" form), referencing `System.Private.CoreLib` is "fine". Update `jnimarshalmethod-gen` assembly location probing: in #1153, it was attempting to resolve the *full assembly name* of `Java.Base`, as `Java.Base, Version=7.0.0.0, Culture=neutral, PublicKeyToken=null`, causing it to attempt to load the file `Java.Base, Version=7.0.0.0, Culture=neutral, PublicKeyToken=null.dll`, which doesn't exist. Use `AssemblyName` to parse the string and extract out the assembly name, so that `Java.Base.dll` is probed for and found. Update `JreTypeManager` to *also* register the marshal methods generated by `Runtime.MarshalMemberBuilder.GetExportedMemberRegistrations()`. With all that, the updated `Java.Interop.Export-Tests` test now work both before and after `jnimarshalmethod-gen` is run: % dotnet test --logger "console;verbosity=detailed" bin/TestDebug-net7.0/Java.Interop.Export-Tests.dll && dotnet bin/Debug-net7.0/jnimarshalmethod-gen.dll bin/TestDebug-net7.0/Java.Interop.Export-Tests.dll && dotnet test --logger "console;verbosity=detailed" bin/TestDebug-net7.0/Java.Interop.Export-Tests.dll … TODO: * `generator --codegen-target=JavaInterop1` should emit JNI method signature information for constructors! This would likely remove the need for `[JavaCallableConstructor(SuperConstructorExpression="")`. * dotnet/java-interop#1159 [0]: https://learn.microsoft.com/dotnet/core/deploying/native-aot [^0]: In a .NET Android environment, marshal methods are part of `generator` output, so things would be more straightforward there, though all the `_JniMarshal_*` types that are declared would also need to have `[UnmanagedFunctionPointer(CallingConvention.Winapi)]`… [^1]: Why not just declare `toString()` as `native`? Why have the separate `n_`-prefixed version? To make the `#if MONODROID_TIMING` block more consistent within `JavaCallableWrapperGenerator.GenerateMethod()`. It's likely "too late" to *easily* change this now.
Context: e75741e Context: https://xamarin.github.io/bugzilla-archives/23/2367/bug.html Context: https://github.com/xamarin/monodroid/commit/6679dfc62eae462b5acc9deb095d0fa41786f80b Constructors, how do they work? Commit e75741e goes into some details about the interaction between Java constructors and managed-side constructors when the Java constructor is invoked first. What happens when the managed-side constructor is invoked first? class JavaInteropExample : Java.Lang.Object { [JavaCallableConstructor(SuperConstructorExpression="")] public JavaInteropExample (int a, int b) {} } *Before* that code runs (ideally), `jcw-gen` (or equivalent) will run, creating a Java Callable Wrapper for `JavaInteropExample`, and that Java Callable Wrapper (JCW) will contain a constructor: // JCW /* partial */ class JavaInteropExample extends java.lang.Object { public JavaInteropExample(int p0, int p1) { super (); if (getClass () == JavaInteropExample.class) { ManagedPeer.construct (…); } } } Then someone tries: // C# var o = new JavaInteropExample(42); What happens is: 1. `JavaInteropExample(int, int)` constructor begins execution, *immediately* executes (implicit) base constructor invocation `: base()`. 2. `Java.Lang.Object` default constructor executes, which invokes `JavaObject(ref JniObjectReference, JniObjectReferenceOptions)` constructor, which is a no-op as the `JniObjectReference` is invalid. 3. `Java.Lang.Object` default constructor continues, hitting: var peer = JniPeerMembers.InstanceMethods.StartCreateInstance ("()V", GetType (), null); which looks up the Java peer, and invokes the default constructor on the Java peer. 4. `JavaObject.PeerReference` (eventually) is `peer.NewGlobalRef()`, and the `JavaInteropExample` constructor can *now* begin executing. If the JCW doesn't contain a default constructor, then things fail: Error Message: Java.Interop.JavaException : Lnet/dot/jni/test/JavaCallableExample;.<init>()V Stack Trace: at Java.Interop.JniEnvironment.InstanceMethods.GetMethodID(JniObjectReference type, String name, String signature) in /Users/jon/Developer/src/xamarin/java.interop/src/Java.Interop/obj/Debug/net7.0/JniEnvironment.g.cs:line 19947 at Java.Interop.JniType.GetConstructor(String signature) in /Users/jon/Developer/src/xamarin/java.interop/src/Java.Interop/Java.Interop/JniType.cs:line 182 at Java.Interop.JniPeerMembers.JniInstanceMethods.GetConstructor(String signature) in /Users/jon/Developer/src/xamarin/java.interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods.cs:line 63 at Java.Interop.JniPeerMembers.JniInstanceMethods.FinishCreateInstance(String constructorSignature, IJavaPeerable self, JniArgumentValue* parameters) in /Users/jon/Developer/src/xamarin/java.interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods.cs:line 173 at Java.Lang.Object..ctor() in /Users/jon/Developer/src/xamarin/java.interop/src/Java.Base/obj/Debug-net7.0/mcw/Java.Lang.Object.cs:line 33 at Java.InteropTests.JavaCallableExample..ctor(Int32 a) in /Users/jon/Developer/src/xamarin/java.interop/tests/Java.Interop.Export-Tests/Java.Interop/JavaCallableExample.cs:line 11 at Java.InteropTests.JavaCallableExampleTest.ManagedCtorInvokesJavaDefaultCtor() in /Users/jon/Developer/src/xamarin/java.interop/tests/Java.Interop.Export-Tests/Java.Interop/JavaCallableExampleTests.cs:line 22 at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor) at System.Reflection.MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr) --- End of managed Java.Interop.JavaException stack trace --- java.lang.NoSuchMethodError: Lnet/dot/jni/test/JavaCallableExample;.<init>()V But why would the JCW for `JavaInteropExample` contain a default constructor at all? In .NET Android, bound constructors have `[Register]`, and `jcw-gen` will emit constructors based on "visible" `[Register]`ed constructors. This ensures that we get *at least one* constructor that exists in Java, which the C# derived types will need to invoke. `generator --codegen-target=JavaInterop1`-style bindings didn't previously emit anything for bound constructors, preventing `jcw-gen` from performing this same logic. Consequently, the JCW for `JavaInteropExample` *didn't* have a default constructor. Add a new `Java.Interop.JniConstructorSignatureAttribute` type, and update `generator` to emit this attribute on bound constructors. Update `jcw-gen` to support `JniConstructorSignatureAttribute`. This adds a default constructor to `JavaInteropExample`: // JCW /* partial */ class JavaInteropExample extends java.lang.Object { public JavaInteropExample() { super (); if (getClass () == JavaInteropExample.class) { ManagedPeer.construct (…); } } public JavaInteropExample(int p0, int p1) { super (); if (getClass () == JavaInteropExample.class) { ManagedPeer.construct (…); } } } Note: while there is a public default constructor on the JCW, Java code cannot call it. If it attempts to do so, an exception will be thrown because the C#-side type doesn't have a default constructor.
Changes: dotnet/android-tools@8a971d9...8d38281 * dotnet/android-tools@8d38281: Update the maximum NDK version to 26 (dotnet/android-tools#219) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Fixes: dotnet/java-interop#1159 Context: c6faab7 `jnimarshalmethod-gen` is intended to generate marshal methods for any type that that: * Has `[JavaCallable]` (tested in `Java.Interop.Export-Tests.dll` and c6faab7), or * Overrides a `virtual` method which has `[JniMethodSignature]`, or * Implements an interface method which has `[JniMethodSignature]`. Thus, the intention was that it should generate marshal methods for `Java.Base-Tests`: % dotnet bin/Debug-net7.0/jnimarshalmethod-gen.dll -v bin/TestDebug-net7.0/Java.Base-Tests.dll Unable to read assembly 'bin/TestDebug-net7.0/Java.Base-Tests.dll' with symbols. Retrying to load it without them. Preparing marshal method assembly 'Java.Base-Tests-JniMarshalMethods' Processing Java.BaseTests.JavaInvoker type Processing Java.BaseTests.MyRunnable type Processing Java.BaseTests.MyIntConsumer type Marshal method assembly 'Java.Base-Tests-JniMarshalMethods' created Notably missing? No messages stating: Adding marshal method for … Also missing? `ikdasm bin/TestDebug-net7.0/Java.Base-Tests.dll` showed that there were no `__RegisterNativeMembers()` methods emitted. The `jnimarshalmethod-gen` invocation was a no-op! The problems were twofold: 1. It was only looking for methods with `Android.Runtime.RegisterAttribute`. This was useful for Xamarin.Android (when we were trying to make it work), but doesn't work with Java.Base. We need to *also* look for `Java.Interop.JniMethodSignature`. Relatedly, the attempt to use `registerAttribute.Constructor.Parameters` to determine parameter names didn't work; the parameter name was always `""`. 2. A'la c6faab7, we need to ensure that the Java `native` methods we register are consistent with `jcw-gen` output. Fix these two problems, which allows `jnimarshalmethod-gen` to now emit marshal methods for types within `Java.Base-Tests.dll`. Additionally, rework the `jnimarshalmethod-gen -f` logic to *remove* the existing `__<$>_jni_marshal_methods` nested type when present.
The eventual plan is to move the xamarin/Java.Interop repo to dotnet/jni (timeline: unspecified), and existing Java code within dotnet/runtime (for use with .NET Android) already uses a `net.dot` package-name prefix. As "xamarin" is increasingly "persona-non-grata", *and* these types aren't actually used publicly -- .NET Android née Xamarin.Android has it's own set of `mono.android` types -- we can safely rename the `com.xamarin.java_interop` and related packages to instead use a `net.dot.jni` prefix.
…1170) Fixes: dotnet/java-interop#1169 Context: 76ab8b2 76ab8b2c mentions: > Now that we are in the .NET `TargetFramework` world we also need > to ensure we do not *add* any new API to a Target Framework once it > has shipped. A `TargetFramework` is essentially a contract that we > cannot change. (Imagine if you had different minor versions of .NET > on your local machine and CI machine, what works on one should work > on the other.) This prevents an issue where a user on `.NET 8.0.300` uses a method that isn't available to their coworker or CI on `.NET 8.0.100`. This logical argument also applies to `Java.Interop.dll`. Enable Microsoft's [PublicApiAnalyzers][0] for `Java.Interop.dll`. ([PublicApiAnalyzers documentation][1].) This ensures that we don't add new API once we've shipped `Java.Interop.dll` for a given .NET version. Update `build-tools/jnienv-gen` so that `JniEnvironment.g.cs` enables nullable reference types. This allows us to *avoid* disabling RS0041. Co-authored-by: Jonathan Pryor <jonpryor@vt.edu> [0]: https://github.com/dotnet/roslyn-analyzers/tree/ace28a1039d09626a94d3b0f8ce69e547ca2bcbf/src/PublicApiAnalyzers [1]: https://github.com/dotnet/roslyn-analyzers/blob/ace28a1039d09626a94d3b0f8ce69e547ca2bcbf/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md
Fixes: dotnet/java-interop#1165 Context: dotnet/java-interop#1153 Context: dotnet/java-interop#1157 Context: 522ce71 When building for NativeAOT (#1153) or when building .NET Android apps with `-p:IsAotcompatible=true` (#1157), we get [IL2057][0] warnings from `ManagedPeer.cs`: ManagedPeer.cs(93,19,93,112): warning IL2057: Unrecognized value passed to the parameter 'typeName' of method 'System.Type.GetType(String, Boolean)'. It's not possible to guarantee the availability of the target type. ManagedPeer.cs(156,18,156,65): warning IL2057: Unrecognized value passed to the parameter 'typeName' of method 'System.Type.GetType(String, Boolean)'. It's not possible to guarantee the availability of the target type. ManagedPeer.cs(198,35,198,92): warning IL2057: Unrecognized value passed to the parameter 'typeName' of method 'System.Type.GetType(String, Boolean)'. It's not possible to guarantee the availability of the target type. These warnings are because `ManagedPeer.Construct()` and `ManagedPeer.RegisterNativeMembers()` use `Type.GetType()` on string values provided *from Java code*, and thus the IL trimmer does not have visibility into those strings, and thus cannot reliably determine which types need to be preserved: // Java Callable Wrapper /* partial */ class ManagedType { public static final String __md_methods; static { __md_methods = "n_GetString:()Ljava/lang/String;:__export__\n" + ""; net.dot.jni.ManagedPeer.registerNativeMembers ( /* nativeClass */ ManagedType.class, /* assemblyQualifiedName */ "Example.ManagedType, Hello-NativeAOTFromJNI", /* methods */ __md_methods); } public ManagedType (int p0) { super (); if (getClass () == ManagedType.class) { net.dot.jni.ManagedPeer.construct ( /* self */ this, /* assemblyQualifiedName */ "Example.ManagedType, Hello-NativeAOTFromJNI", /* constructorSignature */ "System.Int32, System.Runtime", /* arguments */ new java.lang.Object[] { p0 }); } } } `ManagedPeer.construct()` passes *two* sets of assembly-qualified type names: `assemblyQualifiedName` contains the type to construct, while `constructorSignature` contains a `:`-separated list of assembly-qualified type names for the constructor parameters. Each of these are passed to `Type.GetType()`. `ManagedPeer.registerNativeMembers()` passes an assembly-qualified type name to `ManagedPeer.RegisterNativeMembers()`, which passes the assembly-qualified type name to `Type.GetType()` to find the type to register native methods for. If we more strongly rely on JNI signatures, we can remove the need for Java Callable Wrappers to contain assembly-qualified type names entirely, thus removing the need for `ManagedPeer` to use `Type.GetType()`, removing the IL2057 warnings. For `ManagedPeer.construct()`, `assemblyQualifiedName` can be replaced with getting the JNI type signature from `self.getClass()`, and `constructorSignature` can be replaced with a *JNI method signature* of the calling constructor. For `ManagedPeer.registerNativeMembers()`, `assemblyQualifiedName` can be replaced with getting the JNI type signature from `nativeClass`. `jcw-gen --codegen-target=JavaInterop1` output becomes: // New JavaInterop1 Java Callable Wrapper /* partial */ class ManagedType { public static final String __md_methods; static { __md_methods = "n_GetString:()Ljava/lang/String;:__export__\n" + ""; net.dot.jni.ManagedPeer.registerNativeMembers ( /* nativeClass */ ManagedType.class, /* methods */ __md_methods); } public ManagedType (int p0) { super (); if (getClass () == ManagedType.class) { net.dot.jni.ManagedPeer.construct ( /* self */ this, /* constructorSignature */ "(I)V", /* arguments */ new java.lang.Object[] { p0 }); } } } This does not alter `jcw-gen --codegen-target=XAJavaInterop1` output; .NET Android will continue to require `Type.GetType()` calls within xamarin/xamarin-android, e.g. [`AndroidTypeManager.RegisterNativeMembers()`][2]. Furthermore, if we add `[DynamicallyAccessedMembers]` to `JniRuntime.JniTypeManager.GetType()`, we can fix some [IL2075][1] warnings which appeared after fixing the IL2057 warnings. Aside: Excising assembly-qualified type names from Java Callable Wrappers had some "interesting" knock-on effects in the unit tests, requiring that more typemap information be explicitly provided. (This same information was *implicitly* provided before, via the provision of assembly-qualified type names everywhere…) One problem with the approach of using JNI signatures instead of using assembly-qualified names is *ambiguity*: there can be multiple managed types which correspond to a given JNI signature. Consider the JNI signature `[I`, which is a Java `int[]`. This is bound as: * C# `int[]` * `JavaArray<int>` * `JavaPrimitiveArray<int>` * `JavaInt32Array` How do we know which to use? Using assembly-qualified type names for constructor parameters nicely solved this issue, but if we're not using them anymore… Update `JavaCallableExample` to demonstrate this: partial class JavaCallableExample { [JavaCallableConstructor(SuperConstructorExpression="")] public JavaCallableExample (int[] a, JavaInt32Array b); } The intention is twofold: 1. This should result in a Java Callable Wrapper constructor with signature `JavaCallableExample(int[] p0, int[] p1)`, and 2. Java code should be able to invoke this constructor. Turns out, neither of these worked when `Type.GetType()` is not used for constructor argument lookup: `JavaCallableWrapperGenerator` didn't fully support e.g. `[JniTypeSignature("I", ArrayRank=1)]` (present on `JavaInt32Array`), so it didn't know what to do with the `JavaInt32Array` parameter. Once (1) was fixed, (2) would fail because `JniRuntime.JniTypeManager.GetType(JniTypeSignature.Parse("[I"))` would return `JavaPrimitiveArray<int>`, which wasn't used in `JavaCallableExample`, resulting in: System.NotSupportedException : Unable to find constructor Java.InteropTests.JavaCallableExample(Java.Interop.JavaPrimitiveArray`1[[System.Int32, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]], Java.Interop.JavaPrimitiveArray`1[[System.Int32, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]). Please provide the missing constructor. ----> Java.Interop.JniLocationException : Exception of type 'Java.Interop.JniLocationException' was thrown. Stack Trace: at Java.Interop.ManagedPeer.GetConstructor(JniTypeManager typeManager, Type type, String signature, Type[]& parameterTypes) at Java.Interop.ManagedPeer.Construct(IntPtr jnienv, IntPtr klass, IntPtr n_self, IntPtr n_constructorSignature, IntPtr n_constructorArguments) … --- End of managed Java.Interop.JavaException stack trace --- java.lang.Throwable at net.dot.jni.ManagedPeer.construct(Native Method) at net.dot.jni.test.JavaCallableExample.<init>(JavaCallableExample.java:32) at net.dot.jni.test.UseJavaCallableExample.test(UseJavaCallableExample.java:8) The constructor couldn't be found because `JniRuntime.JniTypeManager.GetTypes()` was incomplete, which is a longstanding limitation from 522ce71: for `[I`, it would only return `JavaPrimitiveArray<int>` and `int[]`, in that order. Fix both of these. `JniRuntime.JniTypeManager.GetTypes(JniTypeSignature.Parse("[I"))` will now include: * `JavaArray<int>` * `JavaPrimitiveArray<int>` * `JavaInt32Array` * `int[]` This now allows the `JavaCallableExample` constructor to be invoked from Java. Because `ManagedPeer.Construct()` is now doing so much extra work in order to find the `ConstructorInfo` to invoke, cache the lookups. (Technically this is a "memory leak," as cache entries are never removed.) Finally, update `CecilCompilerExpressionVisitor` to emit `newobj` in certain `VisitNew()` invocations. This was needed while trying: partial class JavaCallableExample { [JavaCallable ("getA")] public int[] GetA() => this.a; } in order to fix the IL error: % $HOME/.dotnet/tools/ilverify bin/TestDebug-net7.0/Java.Interop.Export-Tests.dll \ --tokens --system-module System.Private.CoreLib \ -r 'bin/TestDebug-net7.0/*.dll' \ -r '/usr/local/share/dotnet/shared/Microsoft.NETCore.App/7.0.10/*.dll' [IL]: Error [StackUnderflow]: […/bin/TestDebug-net7.0/Java.Interop.Export-Tests.dll : .__<$>_jni_marshal_methods::n_GetA(native int, native int)][offset 0x0000002F] Stack underflow. Unfortunately, even after the above fix invalid IL was generated during `jnimarshalmethod-gen` processing, which will be investigated later. [0]: https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/trim-warnings/IL2057 [1]: https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/trim-warnings/il2075 [2]: https://github.com/xamarin/xamarin-android/blob/main/src/Mono.Android/Android.Runtime/AndroidRuntime.cs#L481-L577
Changes: dotnet/android-tools@8d38281...4889bf0 * dotnet/android-tools@4889bf0: [MSBuildReferences.projitems] Require opt-in to use `Microsoft.Build` (dotnet/android-tools#220) * dotnet/android-tools@21de3d7: [build] update $(MSBuildPackageReferenceVersion) to 17.6.3 (dotnet/android-tools#221) * dotnet/android-tools@08a6990: Bump android-sdk NDK version to 26.1.10909125 * dotnet/android-tools@6ae1f2a: Bump android-sdk build-tool version to 34.0.0 * dotnet/android-tools@184b6b3: Bump android-sdk cmdline-tools to version 11.0 * dotnet/android-tools@1365e33: Bump android-sdk platforms-tools to version 34.0.5 Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Building the `Java.Interop.sln` solution always produces some generated files that `git` wants to try to commit: Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: tests/invocation-overhead/jni.cs Untracked files: (use "git add <file>..." to include in what will be committed) tests/invocation-overhead/jni-api.h tests/invocation-overhead/jni.c Commit these generated files, and update the generator for `jni.cs` to always use native platform new lines so it does not show up as modified on Windows.
Context: https://devdiv.visualstudio.com/DevDiv/_wiki/wikis/DevDiv.wiki/25351/APIScan-step-by-step-guide-to-setting-up-a-Pipeline The ApiScan task has been added to pipeline runs against `main`. This task should help us identify related issues earlier, rather than having to wait for a full scan of VS.
Changes: dotnet/android-tools@4889bf0...ed102fc * dotnet/android-tools@ed102fc: [Xamarin.Android.Tools.Versions] Add JavaSdkVersion (#226) * dotnet/android-tools@b175674: [ci] Only enable CodeQL on Windows build job (#224) * dotnet/android-tools@2a2e64b: [ci] Add API Scan job (#225) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
The two `api-24.xml.in` files were ~28 MiB each (56 MiB total) and nearly identical: only 5 `annotated-visibility="TESTS"` markers (used by `JavaTypeModelsTests.AnnotatedVisibility`) differed. Consolidate them into a single shared file under `tests/TestData/` and strip it down to ~390 KB while keeping all 30 tests (17 in `Java.Interop.Tools.JavaTypeSystem-Tests` + 13 in `Xamarin.Android.Tools.ApiXmlAdjuster-Tests`) passing. Changes: * Move `Java.Interop.Tools.JavaTypeSystem-Tests/api-24.xml.in` to `tests/TestData/api-24.xml.in` (preserves the TESTS annotations). * Delete the duplicate copy under `Xamarin.Android.Tools.ApiXmlAdjuster-Tests`. * Point both `JavaApiTestHelper.cs` files at the shared path. * `Xamarin.Android.Tools.ApiXmlAdjuster`'s loader does not recognize `annotated-visibility`; strip the attribute in-memory before parsing in that test project's helper. * Trim the shared XML to the transitive type-resolution closure needed by the test assertions (685 types out of 3823), and additionally strip method/field/constructor bodies from types whose bodies aren't inspected by any test, keeping bodies only on a small allowlist (`ContentObservable`, `Observable`, `ConcurrentHashMap`, `Activity`, `StateListAnimator`, `DrmStore.ConstraintsColumns`). Net repo size reduction: ~55.6 MiB. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
## Summary Register JNI native methods by marshalling into the **blittable** `JniNativeMethod` struct and calling the existing `RegisterNatives(JniObjectReference, ReadOnlySpan<JniNativeMethod>)` overload, instead of invoking the JNI `RegisterNatives` function pointer with a **non-blittable managed array** (`JniNativeMethodRegistration[]`). This avoids relying on the runtime to marshal an array of a non-blittable struct across a `delegate* unmanaged<>` call — a path that is currently miscompiled by crossgen2 under composite ReadyToRun + PGO and corrupts the registered method names. Refs #11633 ## Background: what breaks The default (non-trimmable / llvm-ir typemap) registration funnels through: ```csharp // JniEnvironment.Types public static void RegisterNatives (JniObjectReference type, JniNativeMethodRegistration [] methods, int numMethods) => _RegisterNatives (type, methods, numMethods); // generated invoker: // delegate* unmanaged<IntPtr, jobject, JniNativeMethodRegistration[], int, int> RegisterNatives ``` `JniNativeMethodRegistration` is non-blittable (`string Name; string Signature; Delegate Marshaler`). Passing `JniNativeMethodRegistration[]` through the `delegate* unmanaged<>` requires the runtime to marshal the array element-by-element. `ManagedPeer..cctor`, `AndroidTypeManager`, `ManagedTypeManager`, and every `JniType.RegisterNativeMethods` caller funnel through this single method (`_RegisterNatives` has exactly one caller), so fixing it here fixes all of them. ## Root cause (a crossgen2 / runtime regression) dotnet/runtime **#126911** ("Move built-in array marshalling to managed", 2026-05-01) moved array-of-struct marshalling from native C++ into the managed generic `System.StubHelpers.StructureMarshaler<T> : IArrayElementMarshaler<T, StructureMarshaler<T>>`. Its element converter is an intrinsic with a **blittable-only fallback body**: ```csharp // src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs // "Non-blittable structs should have a custom IL body generated with the marshaling logic." [Intrinsic] private static void ConvertToUnmanagedCore (ref T managed, byte* unmanaged, ref CleanupWorkListElement? cleanupWorkList) => SpanHelpers.Memmove (ref *unmanaged, ref Unsafe.As<T,byte>(ref managed), (nuint)sizeof(T)); ``` For non-blittable `T`, the VM generates a real per-field marshalling body at JIT time (`StructMarshalStubs::TryGenerateStructMarshallingMethod`, `dllimport.cpp`). **crossgen2 has no equivalent**, so when PGO/MIBC marks the marshaller hot and crossgen2 precompiles the shared canonical (`__Canon`) instantiation, it emits the literal `Memmove` fallback — a raw blit of the managed object references into the native struct. Disassembly of the precompiled canonical converter from a composite-R2R + MIBC image: ```asm StructureMarshaler`1<__Canon>.ConvertToUnmanagedCore: ldr x0, [x1] ; first 8 bytes of the managed struct = the Name string REFERENCE str x0, [x2] ; stored straight into the native JNINativeMethod.name ← no string→char* marshalling ret ``` So JNI receives a managed `string` object pointer where it expects a UTF-8 `char*`, the method name is garbage, and registration fails with `NoSuchMethodError` during startup (e.g. `MauiApplication`, `net.dot.jni.ManagedPeer`). This only manifests when crossgen2 precompiles the marshaller (MIBC marks it hot). Without a profile the JIT compiles it and registration is correct — which is why the same app works without a startup MIBC. ## Why this is the right fix - It **eliminates the non-blittable array marshalling** at this call site entirely. The blittable `RegisterNatives(ReadOnlySpan<JniNativeMethod>)` path marshals names/signatures to UTF-8 ourselves (`Marshal.StringToCoTaskMemUTF8`) and passes a blittable `JniNativeMethod*` — there is no `StructureMarshaler<T>` involved, so it is correct regardless of the crossgen2 bug. - It matches the **trimmable type-map path**, which already used the blittable overload and was therefore never affected. - It is the single registration chokepoint, so it fixes `ManagedPeer`, `AndroidTypeManager`, and `ManagedTypeManager` together. We do **not** want to marshal arrays of non-blittable types across `delegate* unmanaged<>` / P/Invoke boundaries given this runtime limitation. A scan of the shipped runtime path (Java.Interop + Mono.Android) shows `RegisterNatives` was the only such site: it is the only invoker function pointer with an array parameter, the only non-blittable-array native call; all `JValue[]` call sites already pin (`fixed (JValue* …)`) and pass a blittable pointer. ## Changes - Rework `JniEnvironment.Types.RegisterNatives(JniObjectReference, JniNativeMethodRegistration[], int)` to marshal `Name`/`Signature` to unmanaged UTF-8 and a function pointer, then dispatch to the blittable `RegisterNatives(JniObjectReference, ReadOnlySpan<JniNativeMethod>)` overload (no more `_RegisterNatives` / non-blittable `delegate* unmanaged<>` call). - Preserve JNI error behavior in the blittable overload: it now observes and rethrows (clearing) any pending Java exception from `JNIEnv::RegisterNatives()` — e.g. `NoSuchMethodError` — and guards `type.IsValid`, matching the generated `_RegisterNatives` wrapper it replaces. This benefits both the array-based path and the trimmable type-map path (which previously could leave a pending exception in the JNIEnv). - Add regression tests for the `JniNativeMethodRegistration[]` path to validate correct UTF-8 marshaling and function pointer conversion, covering both stack-allocated (≤32 methods) and heap-allocated (>32 methods) code paths. ## Implementation notes - UTF-8 name/signature buffers are allocated with `Marshal.StringToCoTaskMemUTF8` and freed in a `finally` block with `Marshal.ZeroFreeCoTaskMemUTF8`, guarding against `IntPtr.Zero` to prevent crashes with uninitialized entries. `GC.KeepAlive (methods)` keeps the marshaler delegates alive across the native call. - Null `Marshaler` delegates are explicitly checked with an actionable error message that includes the array index and method signature, rather than relying on the generic `ArgumentNullException` from `Marshal.GetFunctionPointerForDelegate`. - `Marshal.GetFunctionPointerForDelegate` is called inline within the already-`RequiresDynamicCode`-annotated method. This `JniNativeMethodRegistration[]` path runs only on JIT-capable runtimes (MonoVM/CoreCLR); NativeAOT registers through the trimmable type map with statically-compiled function pointers and never reaches it. ## Verification Reproduced and fixed in an isolated `net11.0` console app (no Java.Interop/Android types): a non-blittable `struct[]` passed through a `delegate* unmanaged<>` to a real native function corrupts only under composite R2R + MIBC (when the call site is hot); the blittable equivalent is correct under all configurations (JIT, plain R2R, composite R2R, composite R2R + MIBC). ## Tracking - Underlying runtime regression introduced by dotnet/runtime#126911; durable fix belongs in crossgen2 (expand the marshalling intrinsic for non-blittable `__Canon`, or defer it to the JIT). This PR is the Java.Interop-side fix and is correct independent of the runtime change.
#1483) Fixes #1335. ## Why [JSpecify](https://jspecify.dev/) is becoming the standard way Java libraries (AndroidX, Guava, etc.) declare nullness. Unlike the older `@NonNull`/`@Nullable` declaration annotations we already recognize, JSpecify uses `TYPE_USE` annotations plus a scope-default model (`@NullMarked` on a package or class means "every reference here is non-null unless marked `@Nullable`"). The bytecode parser was missing two pieces required to consume any of this: it never read JSR 308 type annotations, and it had no notion of a scope default. As a result, generated bindings for JSpecify-annotated Java libraries were emitting reference types without `not-null="true"`, which downstream becomes nullable C# references. ## Approach This change adds the minimum useful slice so that most APIs in a JSpecify-annotated library get correct C# nullable reference type annotations: - **JSR 308 type-annotation parsing.** New `TypeAnnotation` type and `RuntimeVisible/InvisibleTypeAnnotationsAttribute` classes that fully parse the `target_info` discriminated union (every variant is read past, even ones we don't act on yet, so the stream stays aligned) and skip past `type_path` while remembering its length. - **Scope detection.** `ClassPath` now retains `package-info.class` files and exposes `GetPackageInfo(packageName)`. `XmlClassDeclarationBuilder` computes `isNullMarked` from the class's own `@NullMarked`/`@NullUnmarked` plus the package-info fallback, reading from both `RuntimeVisibleAnnotations` and `RuntimeInvisibleAnnotations` (JSpecify scope annotations are `@Retention(RUNTIME)`). - **Nullness resolution.** New `GetMethodReturnNullness`/`GetParameterNullness`/`GetFieldNullness` helpers combine declaration-level `@NonNull`, top-level `TYPE_USE` `@Nullable`/`@NonNull` annotations, and the scope default. Reference-typed slots in a null-marked scope default to `not-null="true"`; explicit `@Nullable` opts back out. ## Trade-offs and deliberately deferred To keep the change small enough to land, the following are out of scope and tracked as known limitations (one test, `PackageMarked_NullUnmarkedMethod_RevertsToUnknown`, pins down the current behavior so it's easy to find when extending): - Method-level `@NullUnmarked` is not honored (only class and package scope). - Module-level `@NullMarked` is not honored. - Inner-class scope inheritance from the enclosing class is not implemented. - Type-path-aware nullness (e.g. `Map<@nullable K, V>` for inner generic type arguments) is intentionally only respected at the top of the type. The parser reads `type_path` correctly so `@Nullable String[]` vs `String @nullable[]` are distinguished by `AppliesToTopLevelType` (`type_path_length == 0`), which is the case that matters for surface API nullness. ## Tests 10 new tests in `JSpecifyAnnotationTests.cs` covering package-level `@NullMarked`, class-level `@NullMarked`, primitive exclusion, `@Nullable` opt-out at each slot kind, control unmarked classes, and direct verification that the new type-annotation attribute parses. All 92 `Xamarin.Android.Tools.Bytecode-Tests` pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…1447) Related to #1448 ## Summary - Remove the standalone `src/Java.Runtime.Environment` project and the desktop/JRE runtime path it provided. - Remove the samples, performance tests, dynamic tests, and Java.Base tests that depended on the old standalone runtime surface. - Keep `Java.Interop-Tests` in the solution and CI, and restore them to a passing state without bringing back the `Java.Runtime.Environment` assembly. - Remove the `Java.Runtime.Environment` friend-assembly dependency from `Java.Interop`; keep only the `Java.Interop-Tests` friend access needed by the restored tests. - Trim `src/java-interop` down to the native source subset still consumed by dotnet/android, while keeping a minimal validation project so those sources continue to compile in this repo. - Keep a minimal `Hello-NativeAOTFromJNI` smoke sample that proves Java can load a NativeAOT library, initialize `JniRuntime` from an existing `JNIEnv*`, and call into a native C# method. ## Note on `tests/TestJVM/TestJVM.cs` This file intentionally grew because it now owns the small amount of JVM-hosting/runtime plumbing that `Java.Interop-Tests` still need after deleting `Java.Runtime.Environment`. The new code is test-only and replaces the old `TestJVM : JreRuntime` dependency with a direct `TestJVM : JniRuntime` host. It keeps only the pieces needed by the core tests: - `JNI_CreateJavaVM` loading through `NativeLibrary` - classpath setup for `java-interop.jar` and `interop-test.jar` - JDK/JVM path discovery from `JdkInfo.props` or installed JDKs - a minimal object reference manager for global/weak-global reference counts - a minimal managed value manager for peer registration/lookup/finalization - a test type manager for the explicit Java type mappings used by the suite It deliberately does **not** reintroduce the old product/runtime API surface such as `JreRuntime`, `JreRuntimeOptions`, Mono runtime managers, standalone runtime packaging, reference-log plumbing, or the native `java-interop` JVM loader path. The restored tests/test host have no remaining `Java.Runtime.Environment` references. ## Note on `samples/Hello-NativeAOTFromJNI` The old sample depended on `Java.Runtime.Environment`, so this PR restores it as a smaller smoke test instead of bringing back the old sample wholesale. The new sample: - publishes a NativeAOT shared library, - lets Java load it with `System.loadLibrary("Hello-NativeAOTFromJNI")`, - initializes a sample-local `JniRuntime` from the existing `JNIEnv*`, and - calls a native C# `sayHello()` method that returns a Java string. It does not exercise managed peer construction or generated JCWs; those can be added later with explicit support if we want a broader NativeAOT/JNI sample. ## Validation - `dotnet build -t:Prepare` - `dotnet build src/java-interop/java-interop.csproj` - `dotnet build src/Java.Interop/Java.Interop.csproj --no-restore` - `dotnet build tests/Java.Interop-Tests/Java.Interop-Tests.csproj --no-restore` - `dotnet test tests/Java.Interop-Tests/Java.Interop-Tests.csproj --no-build` — 669 passed, 0 failed, 4 skipped - `dotnet publish -c Release -r osx-arm64 samples/Hello-NativeAOTFromJNI/Hello-NativeAOTFromJNI.csproj` - `dotnet build -c Release -r osx-arm64 -t:RunJavaSample samples/Hello-NativeAOTFromJNI/Hello-NativeAOTFromJNI.csproj --no-restore` Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Jonathan Peppers <jonathan.peppers@microsoft.com>
5bf0a0e to
220f39b
Compare
Bumps [external/xamarin-android-tools](https://github.com/xamarin/xamarin-android-tools) from `5165523` to `bcce35f`. - [Commits](dotnet/android-tools@5165523...bcce35f) --- updated-dependencies: - dependency-name: external/xamarin-android-tools dependency-version: bcce35f51c67671ca4846fc8ea0dbe140ea9542a dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
220f39b to
71db2de
Compare
Output Binary ComparisonCompared the artifacts from this PR's dotnet/android binaries — effectively identical
Per-package breakdown of Java.Interop lanes vs upstream
|
2f63128 to
7fc6649
Compare
Adds a shared 'Java.Interop Tests' stage template at build-tools/automation/yaml-templates/stage-java-interop-tests.yaml that runs the two jobs from the upstream java-interop pipeline (Windows - .NET, Mac - .NET) using the in-tree external/Java.Interop/build-tools/automation/templates/ files. The stage is wired into both the official pipeline (azure-pipelines.yaml) and the public PR validation pipeline (azure-pipelines-public.yaml) so they stay in lockstep with zero duplication. A follow-up PR will reorganize external/Java.Interop and dedupe its nested external/* submodules against dotnet/android's own external/.
7fc6649 to
41c5962
Compare
dotnet/android does not normally allow merge commits. The maintainer landing
this PR needs to JIT-elevate to temporarily enable the merge-commit
option, land the PR, and then revert the setting. If this PR is squashed or
rebased, the full per-commit authorship history from dotnet/java-interop is
lost.
What this does
Replaces the
external/Java.Interopgit submodule with an in-tree copy ofthe entire
dotnet/java-interophistory, rewritten so every commit's treelives under
external/Java.Interop/.dotnet/java-interop@main, up to andincluding the SHA the submodule pointer currently references on
dotnet/android/main(6ec1345165)preserved verbatim on every commit — no
Co-authored-bytrailers added(dates span 2014-01-02 .. 2026-06-26)
external/Java.Interop/); this is inherent to subdirectory filteringHow it was done
then in this branch:
Commits on this branch (3 on top of
origin/main)Adjustments commit — what's in it
The single follow-up commit on top makes the minimal changes needed for
in-tree paths + CI:
.gitmodules— dropexternal/Java.Interop, add nestedexternal/Java.Interop/external/xamarin-android-toolsbuild-tools/scripts/XAVersionInfo.targets— drop Java.Interop from_SubmoduleBranchInfosrc/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.targets—@JAVA_INTEROP_COMMIT@→ literalin-treebuild-tools/automation/yaml-templates/cache-gradle.yaml— usegit log -1 --format=%H external/Java.Interopinstead of submodule statusbuild-tools/automation/yaml-templates/stage-java-interop-tests.yaml(see CI changes below)
external/Java.Interop/build-tools/automation/templates/{core-build,core-tests,run-dotnet-test}.yaml— add
workingDirectory: external/Java.Interopand full-path projectglobs so the templates work when driven from the dotnet/android repo root
external/Java.Interop/(dependabot,GitHub Actions workflows,
onelocbuild.yaml, the upstreamazure-pipelines.yaml,publish-test-results.yaml) — none are wiredinto anything on the dotnet/android side
CI changes
Adds a shared stage template
build-tools/automation/yaml-templates/stage-java-interop-tests.yamlthatmirrors the two jobs from the upstream ji pipeline:
nativeAotRid: win-x64nativeAotRid: osx-arm64(AppleSilicon), with a guarded
brew install cmakestepThe stage is wired into both pipelines (zero duplication):
build-tools/automation/azure-pipelines.yaml(official / 1ES) — usesdefault hosted pools
build-tools/automation/azure-pipelines-public.yaml(public PRvalidation) — Windows on NetCore-Public
(
WindowsPoolImageNetCorePublic), Mac on AcesShared(
ACES_VM_SharedPool_Tahoe)Build artifact publishing was intentionally dropped from the ji lanes —
only logs + test results are surfaced (matches upstream ji behavior; the
binaries aren't consumed downstream).
Verified
6ec1345165: 1,577 / 1,580 sharedfiles byte-identical. The 3 mismatches are the ji CI templates
intentionally modified for in-tree paths (see above). 9 files
intentionally absent (the deleted-yml list above). 0 files "only in
ours".
baseline build with no ji changes — all 7 artifacts match; largest
delta was 68 bytes on a 145 MB nupkg.
windows-toolchain-pdbbyte-identical.
running the same SHA.
Follow-up PR (not this one)
external/Java.Interop/external/Java.Interop/external/*submodules againstdotnet/android's own
external/*(e.g.xamarin-android-tools)Checklist for the merger